package com.mindskip.xzs.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mindskip.xzs.base.SystemCode;
import com.mindskip.xzs.component.QywxComponent;
import com.mindskip.xzs.constant.CommonRedisKey;
import com.mindskip.xzs.domain.User;
import com.mindskip.xzs.domain.enums.UserStatusEnum;
import com.mindskip.xzs.exception.BusinessException;
import com.mindskip.xzs.repository.NewUserMapper;
import com.mindskip.xzs.service.QywxAuthService;
import com.mindskip.xzs.viewmodel.student.qywx.SessionKeyAndUserIdResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * https://developer.work.weixin.qq.com/document/path/92426
 *  首先由小程序端调用wx.login()去获取code，然后，再通过wx.getUserInfo()去获取用户信息（这里请求login和getUserInfo是一起的，把这两次请求的数据合并发给服务端的login接口），通过请求，把：
 *      1.code //临时登入凭证
 *      // 如果不同意获取用户信息，则下面四个参数获取不到
 *      2.rawData //用户非敏感信息，头像和昵称之类的
 *      3.signature //签名
 *      4.encryteDate //用户敏感信息，需要解密，（包含unionID）
 *      5.iv //解密算法的向量
 *  给到服务端，服务端根据 appid+secret+js_code+grant_type去请求，获取到session_key和openid（这里无法获取unionID），通过session_key，iv来解密encrypteDate获取用户敏感信息和unionID，把用户信息保存到数据库。然后，我们把sesssoin_key和openid保存下来，与token（自定义登入状态）来进行关联，最后把小程序需要的数据返回给小程序端，以后就通过token来维护用户登入状态。
 * </p>
 *
 * @author guoguangkai
 * @since 2022/9/13
 */
@Slf4j
@Service
public class QywxAuthServiceImpl implements QywxAuthService {

    @Resource
    private QywxComponent qywxComponent;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private NewUserMapper newUserMapper;
    /**
     * 小程序token过期时间
     */
    @Value("${system.wx.token-to-live}")
    private Long timeout;

    /**
     * 用户敏感信息：头像、手机号、邮箱，就不授权获取了
     */
    @Override
    public HashMap<String, Object> appletLogin(String appletCode) {
        String accessToken = qywxComponent.getAccessToken();
        /**
         * 开发者服务器使用临时登录凭证code获取 session_key、用户userid以及用户所在企业的corpid等信息。
         *
         * 企业微信的jscode2session请求url与微信的不同
         * 企业微信的jscode2session返回的是userid，而微信返回的是openid
         * 获取access_token时请使用企业的corpid参数，请勿使用小程序的appid
         * 会话密钥 session_key 是对用户数据进行加密签名的密钥，为了应用自身的数据安全，开发者服务器不应该把会话密钥下发到小程序，也不应该对外提供这个密钥。
         */
        String response = HttpUtil.get(CommonRedisKey.GET_SESSION_KEY_AND_USERID_URL, new HashMap<String, Object>(4) {
            private static final long serialVersionUID = 1458810925689454530L;
            {
                // 调用接口凭证(注意，此处的access_token 是企业微信应用的access_token，要求必须由该小程序关联的企业微信应用的secret获取
                put("access_token", accessToken);
                // 登录时获取的 code
                put("js_code", appletCode);
                // 此处固定为authorization_code
                put("grant_type","authorization_code");
            }
        });
        if (StringUtils.isBlank(response)) {
            log.error("远程调用企业微信接口获取sessionkey和userid进行用户认证，http请求返回null");
            throw new BusinessException("与企业微信内部系统通信失败，请稍后重试");
        }
        SessionKeyAndUserIdResult sessionKeyAndUserIdResult;
        try {
            sessionKeyAndUserIdResult = JSONObject.parseObject(response, SessionKeyAndUserIdResult.class);
        } catch (Exception e) {
            log.error("远程调用企业微信接口获取sessionkey和userid进行用户认证，json解析失败，返回原始内容：{}", response);
            throw new BusinessException("与企业微信内部系统通信失败，请稍后重试");
        }
        if (sessionKeyAndUserIdResult == null) {
            log.error("远程调用企业微信接口获取sessionkey和userid进行用户认证失败，http请求返回null");
            throw new BusinessException("与企业微信内部系统通信失败，请稍后重试");
        }
        if (!Long.valueOf(0).equals(sessionKeyAndUserIdResult.getErrCode())) {
            log.error("远程调用企业微信接口获取sessionkey和userid进行用户认证出错，返回code：{}，msg：{}", sessionKeyAndUserIdResult.getErrCode(), sessionKeyAndUserIdResult.getErrMsg());
            throw new BusinessException("与企业微信内部系统通信失败，请稍后重试");
        }

        // 获取openId && sessionKey
        String userId = sessionKeyAndUserIdResult.getUserId();
        String sessionKey = sessionKeyAndUserIdResult.getSessionKey();

        if (StringUtils.isBlank(userId)) {
            log.error("远程调用企业微信接口获取sessionkey和userid进行用户认证，code：{}", appletCode);
            throw new BusinessException("与企业微信内部系统通信失败，请稍后重试");
        }
        if (StringUtils.isBlank(sessionKey)) {
            log.error("远程调用企业微信接口获取sessionkey和userid进行用户认证，返回sessionKey为空，code：{}", appletCode);
            throw new BusinessException("与企业微信内部系统通信失败，请稍后重试");
        }

        // 查询用户信息
        List<User> users = (List<User>) redisTemplate.opsForValue().get(CommonRedisKey.ALL_EMPLOYEE_INFO_KEY);
        User userInfo;
        if (users != null && CollectionUtil.isNotEmpty(users)) {
            userInfo = users.stream().filter(user ->
                    userId.equals(user.getQywxId()) && UserStatusEnum.Enable.getCode() == user.getStatus()
            ).findFirst().orElseGet(() -> {
                // 缓存没命中，以防万一，再查一遍数据库
                User userInDb = newUserMapper.selectOne(new LambdaQueryWrapper<User>()
                        .eq(User::getStatus, UserStatusEnum.Enable.getCode())
                        .eq(User::getQywxId, userId)
                        .eq(User::getDeleted, false));
                if (userInDb != null) {
                    return userInDb;
                } else {
                    throw new BusinessException(SystemCode.XCX_UNAUTH.getCode(), "非企业内部员工或未启用考试系统权限，无权登录");
                }
            });
        } else {
            // 缓存没有，查数据库
            userInfo = newUserMapper.selectOne(new LambdaQueryWrapper<User>()
                    .eq(User::getStatus, UserStatusEnum.Enable.getCode())
                    .eq(User::getQywxId, userId)
                    .eq(User::getDeleted, false)
            );
            if (userInfo == null) {
                throw new BusinessException(SystemCode.XCX_UNAUTH.getCode(), "非企业内部员工或未启用考试系统权限，无权登录");
            }
        }

        // 生成token，并缓存
        String token =  UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(CommonRedisKey.EMPLOYEE_QYWX_XCX_LOGIN_KEY + token, userInfo, timeout, TimeUnit.HOURS);
        return new HashMap<String, Object>(6){
            private static final long serialVersionUID = 4938497273524979361L;
            {
                put("userQywxId", userId);
                put("userName", userInfo.getUserName());
                put("sessionKey", sessionKey);
                put("token", token);
            }
        };
    }
}
